<html><head><meta charset="utf-8"><title>41 突破难点：如何看 Lambda 源码  -慕课专栏</title>
			<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
			<meta name="renderer" content="webkit">
			<meta property="qc:admins" content="77103107776157736375">
			<meta property="wb:webmaster" content="c4f857219bfae3cb">
			<meta http-equiv="Access-Control-Allow-Origin" content="*">
			<meta http-equiv="Cache-Control" content="no-transform ">
			<meta http-equiv="Cache-Control" content="no-siteapp">
			<link rel="apple-touch-icon" sizes="76x76" href="https://www.imooc.com/static/img/common/touch-icon-ipad.png">
			<link rel="apple-touch-icon" sizes="120x120" href="https://www.imooc.com/static/img/common/touch-icon-iphone-retina.png">
			<link rel="apple-touch-icon" sizes="152x152" href="https://www.imooc.com/static/img/common/touch-icon-ipad-retina.png">
			<link href="https://moco.imooc.com/captcha/style/captcha.min.css" rel="stylesheet">
			<link rel="stylesheet" href="https://www.imooc.com/static/moco/v1.0/dist/css/moco.min.css?t=201907021539" type="text/css">
			<link rel="stylesheet" href="https://www.imooc.com/static/lib/swiper/swiper-3.4.2.min.css?t=201907021539">
			<link rel="stylesheet" href="https://static.mukewang.com/static/css/??base.css,common/common-less.css?t=2.5,column/zhuanlanChapter-less.css?t=2.5,course/inc/course_tipoff-less.css?t=2.5?v=201907051055" type="text/css">
			<link charset="utf-8" rel="stylesheet" href="https://www.imooc.com/static/lib/ueditor/themes/imooc/css/ueditor.css?v=201907021539"><link rel="stylesheet" href="https://www.imooc.com/static/lib/baiduShare/api/css/share_style0_16.css?v=6aba13f0.css"></head>
			<body><div id="main">


<div class="main-con hide-menu">
    <!-- 左侧菜单 & 索引 -->
    
    <div class="right-content" style="padding-left: 0px;">
        <div class="container clearfix" id="top" style="width: 1134px; display: block;">
            
            
            <div class="center_con js-center_con l" style="width: 1134px;">
                <div class="article-con">
                                            <!-- 买过的阅读 -->
                        

                    
                    <div class="art-title" style="margin-top: 0px;">
                        41 突破难点：如何看 Lambda 源码  
                    </div>
                    <div class="art-info clearfix">
                        
                        <span class="l">
                            更新时间：2019-11-25 10:00:14
                        </span>
                    </div>
                    <div class="art-top">
                                                <img src="https://img2.mukewang.com/5ddb3518000154d406400359.jpg" alt="">
                                                                        <div class="famous-word-box">
                            <img src="https://www.imooc.com/static/img/column/bg-l.png" alt="" class="bg1 bg">
                            <img src="https://www.imooc.com/static/img/column/bg-r.png" alt="" class="bg2 bg">
                            <div class="famous-word">耐心和恒心总会得到报酬的。<p class="author">——爱因斯坦</p></div>
                        </div>
                                            </div>
                    <div class="art-content js-lookimg">
                        <div id="article_content">
                            <div class="cl-preview-section"><h2 id="引导语" style="font-size: 30px;">引导语</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">大家都知道 Java8 中新增了 Lambda 表达式，使用 Lambda 表达式可以对代码进行大量的优化，用几行代码就可以做很多事情，本章以 Lambda 为例，第一小节说明一下其底层的执行原理，第二小节说明一下 Lambda 流在工作中常用的姿势。</p>
</div><div class="cl-preview-section"><h2 id="demo" style="font-size: 30px;">1 Demo</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">首先我们来看一个 Lambda 表达式的 Demo，如下图：<br>
<img class="" src="https://img.mukewang.com/5dd5fe830001211007440492.png" data-original="//img.mukewang.com/5dd5fe830001211007440492.png" alt="图片描述"></p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">代码比较简单，就是新起一个线程打印一句话，但对于图中 () -&gt; System.out.println ( “ lambda is run “ ) 这种代码，估计很多同学都感觉到很困惑，Java 是怎么识别这种代码的？</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">如果我们修改成匿名内部类的写法，就很清楚，大家都能看懂，如下图：<br>
<img class="" src="https://img.mukewang.com/5dd5fe980001358906980558.png" data-original="//img.mukewang.com/5dd5fe980001358906980558.png" alt="图片描述"></p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">那是不是说  () -&gt; System.out.println ( “ lambda is run “ ) 这种形式的代码，其实就是建立了内部类呢？其实这就是最简单 Lambda 表达式，我们是无法通过 IDEA 看到源码和其底层结构的，下面我们就来介绍几种可看到其底层实现的方式。</p>
</div><div class="cl-preview-section"><h2 id="异常判断法" style="font-size: 30px;">2 异常判断法</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们可以在代码执行中主动抛出异常，打印出堆栈，堆栈会说明其运行轨迹，一般这种方法简单高效，基本上可以看到很多情况下的隐藏代码，我们来试一下，如下图：<br>
<img class="" src="https://img.mukewang.com/5dd5fea9000187c107670699.png" data-original="//img.mukewang.com/5dd5fea9000187c107670699.png" alt="图片描述"></p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">从异常的堆栈中，我们可以看到 JVM 自动给当前类建立了内部类（错误堆栈中出现多次的 $ 表示有内部类），内部类的代码在执行过程中，抛出了异常，但这里显示的代码是 Unknown Source，所以我们也无法 debug 进去，一般情况下，异常都能暴露出代码执行的路径，我们可以打好断点后再次运行，但对于 Lambda 表达式而言，通过异常判断法我们只清楚有内部类，但无法看到内部类中的源码。</p>
</div><div class="cl-preview-section"><h2 id="javap-命令法" style="font-size: 30px;">3 javap 命令法</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">javap 是 Java 自带的可以查看 class 字节码文件的工具，安装过 Java 基础环境的电脑都可以直接执行 javap 命令，如下图：<br>
<img class="" src="https://img.mukewang.com/5dd5fec0000181d909200570.png" data-original="//img.mukewang.com/5dd5fec0000181d909200570.png" alt="图片描述"></p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">命令选项中，我们主要是用-v -verbose 这个命令，可以完整输出字节码文件的内容。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">接下来我们使用 javap 命令查看下 Lambda.class 文件，在讲解的过程中，我们会带上一些关于 class 文件的知识。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们在命令窗口中找到 Lambda.class 所在的位置，执行命令：javap -verbose Lambda.class，然后你会看到一长串的东西，这些叫做汇编指令，接下来我们来一一讲解下（ 所有的参考资料来自 <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4">Java 虚拟机规范</a>，不再一一引用说明）：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">汇编指令中我们很容易找到 Constant pool 打头的一长串类型，我们叫做常量池，官方英文叫做 Run-Time Constant Pool，我们简单理解成一个装满常量的 table ，table 中包含编译时明确的数字和文字，类、方法和字段的类型信息等等。table 中的每个元素叫做 cp<em>info，cp</em>info 由唯一标识 ( tag ) + 名称组成，目前 tag 的类型一共有：<br>
<img class="" src="https://img.mukewang.com/5dd5fedc00012eb605020734.png" data-original="//img.mukewang.com/5dd5fedc00012eb605020734.png" alt="图片描述"></p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">贴出我们解析出来的部分图：<br>
<img class="" src="https://img.mukewang.com/5dd5fee90001bf8118760941.png" data-original="//img.mukewang.com/5dd5fee90001bf8118760941.png" alt="图片描述"></p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">
<p style="font-size: 20px; line-height: 38px;">图中 Constant pool 字样代表当前信息是常量池；</p>
</li>
<li style="font-size: 20px; line-height: 38px;">
<p style="font-size: 20px; line-height: 38px;">每行都是一个 <code>cp_info</code> ，第一列的 #1 代表是在常量池下标为 1 的位置 ；</p>
</li>
<li style="font-size: 20px; line-height: 38px;">
<p style="font-size: 20px; line-height: 38px;">每行的第二列，是 <code>cp_info</code> 的唯一标识 ( tag ) ，比如 Methodref 对应着上表中的 CONSTANT_Methodref（上上图中表格中 value 对应 10 的 tag），代表当前行是表示方法的描述信息的，比如说方法的名称，入参类型，出参数类型等，具体的含义在 Java 虚拟机规范中都可以查询到，Methodref 的截图如下：<br>
<img class="" src="https://img.mukewang.com/5dd5ff070001ff6219441450.png" data-original="//img.mukewang.com/5dd5ff070001ff6219441450.png" alt="图片描述"></p>
</li>
<li style="font-size: 20px; line-height: 38px;">
<p style="font-size: 20px; line-height: 38px;">每行的第三列，如果是具体的值的话，直接显示具体的值，如果是复杂的值的话，会显示 <code>cp_info</code> 的引用，比如说图中标红 2 处，引用两个 13 和 14 位置的 <code>cp_info</code>，13 表示方法名字是 init，14 表示方法无返回值，结合起来表示方法的名称和返回类型，就是一个无参构造器；</p>
</li>
<li style="font-size: 20px; line-height: 38px;">
<p style="font-size: 20px; line-height: 38px;">每行的第四列，就是具体的值了。</p>
</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">对于比较重要的 cp_info 类型我们说明下其含义：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">InvokeDynamic 表示动态的调用方法，后面我们会详细说明；</li>
<li style="font-size: 20px; line-height: 38px;">Fieldref 表示字段的描述信息，如字段的名称、类型；</li>
<li style="font-size: 20px; line-height: 38px;">NameAndType 是对字段和方法类型的描述；</li>
<li style="font-size: 20px; line-height: 38px;">MethodHandle 方法句柄，动态调用方法的统称，在编译时我们不知道具体是那个方法，但运行时肯定会知道调用的是那个方法；</li>
<li style="font-size: 20px; line-height: 38px;">MethodType 动态方法类型，只有在动态运行时才会知道其方法类型是什么。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们从上上图中标红的 3 处，发现 Ljava/lang/invoke/MethodHandles$Lookup，java/lang/invoke/LambdaMetafactory.metafactory 类似这样的代码，MethodHandles 和 LambdaMetafactory 都是 java.lang.invoke 包下面的重要方法，invoke 包主要实现了动态语言的功能，我们知道 java 语言属于静态编译语言，在编译的时候，类、方法、字段等等的类型都已经确定了，而 invoke 实现的是一种动态语言，也就是说编译的时候并不知道类、方法、字段是什么类型，只有到运行的时候才知道。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">比如这行代码：Runnable runnable = () -&gt; System.out.println(“lambda is run”); 在编译器编译的时候 () 这个括号编译器并不知道是干什么的，只有在运行的时候，才会知道原来这代表着的是 Runnable.run() 方法。invoke 包里面很多类，都是为了代表这些 () 的，我们称作为方法句柄（ MethodHandler ），在编译的时候，编译器只知道这里是个方法句柄，并不知道实际上执行什么方法，只有在执行的时候才知道，那么问题来了，JVM 执行的时候，是如何知道 () 这个方法句柄，实际上是执行 Runnable.run() 方法的呢？</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">首先我们看下 simple 方法的汇编指令：<br>
<img class="" src="https://img.mukewang.com/5dd5ff170001644407650528.png" data-original="//img.mukewang.com/5dd5ff170001644407650528.png" alt="图片描述"></p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">从上图中就可以看出 simple 方法中的 () -&gt; System.out.println(“lambda is run”) 代码中的 ()，实际上就是 Runnable.run 方法。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们追溯到 # 2 常量池，也就是上上图中标红 1 处，InvokeDynamic 表示这里是个动态调用，调用的是两个常量池的 cp_info，位置是 #0:#37 ，我们往下找 #37 代表着是 // run:()Ljava/lang/Runnable，这里表明了在 JVM 真正执行的时候，需要动态调用 Runnable.run() 方法，从汇编指令上我们可以看出 () 实际上就是 Runnable.run()，下面我们 debug 来证明一下。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们在上上图中 3 处发现了 LambdaMetafactory.metafactory 的字样，通过查询官方文档，得知该方法正是执行时， 链接到真正代码的关键，于是我们在 metafactory 方法中打个断点 debug 一下，如下图：<br>
<img class="" src="https://img.mukewang.com/5dd5ff2d0001ae9417980859.png" data-original="//img.mukewang.com/5dd5ff2d0001ae9417980859.png" alt="图片描述"></p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">metafactory 方法入参 caller 代表实际发生动态调用的位置，invokedName 表示调用方法名称，invokedType 表示调用的多个入参和出参，samMethodType 表示具体的实现者的参数，implMethod 表示实际上的实现者，instantiatedMethodType 等同于 implMethod。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">以上内容总结一下：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">1：从汇编指令的 simple 方法中，我们可以看到会执行 Runnable.run 方法；</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">2：在实际的运行时，JVM 碰到 simple 方法的 invokedynamic 指令，会动态调用 LambdaMetafactory.metafactory 方法，执行具体的 Runnable.run 方法。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">所以可以把 Lambda 表达值的具体执行归功于 invokedynamic JVM 指令，正是因为这个指令，才可以做到虽然编译时不知道要干啥，但动态运行时却能找到具体要执行的代码。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">接着我们看一下在汇编指令输出的最后，我们发现了异常判断法中发现的内部类，如下图：<br>
<img class="" src="https://img.mukewang.com/5dd5ff450001783a13491048.png" data-original="//img.mukewang.com/5dd5ff450001783a13491048.png" alt="图片描述"></p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">上图中箭头很多，一层一层的表达清楚了当前内部类的所有信息。</p>
</div><div class="cl-preview-section"><h2 id="总结" style="font-size: 30px;">4 总结</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们总结一下，Lambda 表达式执行主要是依靠 invokedynamic 的 JVM 指令来实现，咱们演示的类的全路径为：demo.eight.Lambda 感兴趣的同学可以自己尝试一下。</p>
</div>}
                        </div>
                    </div>
                                            <!-- 买过的阅读 -->
                        <div class="art-next-prev clearfix">
                                                                                                <!-- 已买且开放 或者可以试读 -->
                                    <a href="/read/47/article/882">
                                                                    <div class="prev l clearfix">
                                        <div class="icon l">
                                            <i class="imv2-arrow3_l"></i>
                                        </div>
                                        <p>
                                            40 打动面试官：线程池流程编排中的运用实战   
                                        </p>
                                    </div>
                                </a>
                                                                                                                            <!-- 已买且开放 或者可以试读 -->
                                    <a href="/read/47/article/884">
                                                                    <div class="next r clearfix">
                                        <p>
                                            42 常用的 Lambda 表达式使用场景解析和应用    
                                        </p>
                                        <div class="icon r">
                                            <i class="imv2-arrow3_r"></i>
                                        </div>

                                    </div>
                                </a>
                                                    </div>
                                    </div>
                <div class="comments-con js-comments-con" id="coments_con">
                </div>

                
            </div>
            
            
            

        </div>
    </div>
</div>

<div class="modal modal-jiaQun-new hide" id="modal-jiaQun">
    <div class="inner" style="">
        <div class="modal-close js-close-jiaQun">
            <i class="imv2-close"></i>
        </div>
        <div class="content">
            <img src="https://img1.mukewang.com/5d634d40000119e505400602.jpg">
            <div class="right-info">
                <div class="title">
                    扫码加入慕课Java核心用户群
                </div>
                <div class="desc">
                                            <p class="mb6">验证信息：<span id="joincode">1909271435058473</span><span class="copy js-copy-joincode">复制</span></p>
                                        <p class="mb6">QQ讨论群号：906691736</p>
                                            <p>QQ群URL：<a href="https://jq.qq.com/?_wv=1027&amp;k=55RtSbJ" target="_blank">点击访问</a></p>
                                    </div>
            </div>
            <p class="tip">若遇到搜索不到QQ群或加群失败，请联系客服邮箱:kf@imooc.com</p>
        </div>
    </div>
</div>
 
<!-- 专栏介绍页专栏评价 -->

<!-- 专栏介绍页底部三条评价 -->

<!-- 专栏阅读页弹层目录和介绍页页面目录 -->

<!-- 专栏阅读页发布回复 -->

<!-- 专栏阅读页发布评论 -->

<!-- 专栏阅读页底部评论 -->

<!-- 专栏阅读 单个 评论 -->

<!-- 新增回复和展开三条以外回复 -->

<!-- 立即订阅的弹窗 -->












</div></body></html>